Source Code

Google OAuth 2.0 登录流程

google oauth workflow.jpg
  1. client向server发起GET /auth/google请求

  2. server将client重定向至google oauth登录页https://accounts.google.com/o/oauth2/auth,并附带以下查询参数集:

    • response_type: code 登录后返回授权码
    • client_id: GOOGLE_CLIENT_ID Google控制台中的client_id,用于google识别应用程序
    • redirect_url: http://www.example.com/auth/google/callback 回调URL,必须和Google控制台中设置的回调URL一致
    • scope: ['profile', 'email'] 用户授权使用的数据返回,显示在oauth登录确认页中
  3. 用户在登录页授权登录后,google api server会将client重定向至之前设定的回调URL,并附带authorization code授权码

  4. server收到针对回调URL的GET请求GET /auth/google/callback,从中提取出code授权码,并使用该code向google api server换取access token

    1. 请求地址:https://www.googleapis.com/oauth2/v3/token

    2. 请求参数

      • code: 之前获得的授权码
      • grant_type: authorization_code:指明使用授权码进行验证
      • client_id,同上
      • client_secret,同上
      • redirect_url,同上
    3. 返回值:

      • access_token:用于获取其他google api数据或控制权
      • expires_in:过期时间
      • token_type:指明token类型
      • id_token:一串加密信息,使用base64 decode解密后可以获得用户邮箱email和用户唯一标识符sub等信息
      • refresh_token:access_token过期后,可以使用refresh_token来重新获取新的access_token,而不需要用户重新授权

      可以将上述token信息保存到数据库,即使用户不使用app时也能获取用户信息和代替用户进行google api操作等

  5. server从google api收到用户数据后,可以将用户注册到数据库,然后将用户信息序列化后设置cookie并返回给client,后续client使用该cookie进行身份验证

使用passport middleware可以帮助我们简化中间与google api交互的流程,我们只需要设置以下组件:

  1. GoogleStrategy:设置clientID,clientSecret和callbackURL
  2. verify / register callback function:接收用户数据,并完成后续用户认证和注册等流程
  3. session serialization / deserialization:将用户信息存储到cookie/从cookie解码用户信息
  4. next middleware(或router的callback function):执行后续api数据返回
passportJS google oauth workflow.jpg
passportJS google oauth workflow.jpg

创建google strategy

1
2
3
4
5
6
7
8
9
10
11
// passport-config.js
let myGoogleStrategy = new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://www.example.com/auth/google/callback"
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return done(err, user);
});
});
1
2
3
// index.js
const passport = require("passport");
require("./passportConfig");

Ref1 Ref2

cookie验证流程

session workflow.jpg
session workflow.jpg
  1. Express-session middleware:用于解码从client发来的cookie

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // index.js
    const session = require("express-session");
    app.use(
    session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    })
    );
  2. passport.initialize() middleware:负责从cookie中提取userId

    1
    2
    // index.js
    app.use(passport.initialize());
  3. app.use(passport.session()):使用userId,从数据库中获取user,或将user序列化为cookie

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // passport-config.js
    passport.serializeUser((user, done) => {
    done(null, user.id);
    });

    passport.deserializeUser(async (id, done) => {
    try {
    let user = await User.findById(id);
    done(null, user);
    } catch (error) {
    done(error);
    }
    });
    1
    2
    // index.js
    app.use(passport.session())
  4. Self-defined middleware:使用req.isAuthenticated()函数验证登录

    1
    2
    3
    4
    5
    6
    7
    8
    middlewares.checkAuthenticated = async (req, res, next) => {
    if (req.isAuthenticated()) {
    next();
    } else {
    req.flash("error", "You need to login first");
    res.redirect("/auth/login");
    }
    }